/**
 * @author Steve Sedlmayr
 * @mail ssedlmayr@gmail.com
 * @link http://code.google.com/p/chitin/
 * @version 0.1
 
	The MIT License

	Copyright (c) 2009 Steve Sedlmayr

	Permission is hereby granted, free of charge, to any person obtaining a copy
	of this software and associated documentation files (the "Software"), to deal
	in the Software without restriction, including without limitation the rights
	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	copies of the Software, and to permit persons to whom the Software is
	furnished to do so, subject to the following conditions:

	The above copyright notice and this permission notice shall be included in
	all copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
	THE SOFTWARE.
*/

package srs.chitin.mvstate.base 
{
	import flash.utils.*;
	
	/** 
	 * Base context class for the State pattern. A subclassable Singleton.
	 */
	public dynamic class ContextBase implements IContext, IViewState {
		/**
		 * The Singleton instance of the ContextBase class.
		 */
		private static var _instance:ContextBase;
		
		/** 
		 * A String enumeration of all of this context class's state instances.
		 */
		[ArrayElementType("String")]
		protected var _stateEnumeration:Array;
		
		/** 
		 * An enumeration of state class references used to initialize 
		 * the context object with state instances.
		 */
		[ArrayElementType("Object")]
		protected var _stateEnumerationClasses:Array;
		
		/** 
		 * The current state.
		 */
		protected var _state:IViewState;
		
		/** 
		 * Sets the state.
		 * 
		 * @param value The state to which to set the contex object.
		 */
		public function set state(value:IViewState) : void {
			this._state = value;
		}
		
		/** 
		 * Returns the state.
		 * 
		 * @return Returns the state.
		 */
		public function get state() : IViewState {
			return this._state;
		}
		
		/**
		  * ContextBase class constructor. Calls <code>setStateEnumeration()</code> on a subclass
		  * instance's class and sets the default value for <code>_state</code> equal to the first 
		  * state in the <code>_stateEnumeration</code> array. Throws an error if a client class 
		  * tries to instantiate the class directly.
		  * 
		  * @param enforcer An enforcer class for the Singleton pattern in the
		  * ContextBase class. Each layer of hierarchy from ContextBase should have a corresponding
		  * enforcer class derived from ContextBaseEnforcer with the same degree of inheritance.
		  */
		public function ContextBase(enforcer:ContextBaseEnforcer) : void {
			if ( enforcer != null ) {
				/* Initialize the instance with state instances using 
				   class references and set the default state. */
				getDefinitionByName(getQualifiedClassName(this)).setStateEnumeration(this);
				
				if( this._stateEnumeration != null ) { _state = getStateByName(_stateEnumeration[0]); }
			} else {
				//TO-DO :: get the id param working
				//Throw an error if someone attempts to instantiate directly via the constructor
				throw new Error("PATTERN ERROR: YOU HAVE ATTEMPTED ILLEGAL INSTANTIATION OF A SINGLETON CLASS." + 
					"THIS CONSTRUCTOR REQUIRES A SUBCLASS OF ContextBaseEnforcer DEFINED AS A LOCAL GENERIC IN THE SINGLETON SUBCLASS." + 
					"PLEASE REFER TO YOUR PROJECT DOCUMENTATION AND CONTACT THE UI LEAD AND/OR AUTHOR OF THIS DOCUMENT.");
			}
		}
		
		/**
		 * Returns the Singleton instance for the ContextBase class.
		 * 
		 * @return The Singleton instance for the ContextBase class.
		 */
		public static function getInstance() : ContextBase {
			if ( _instance == null ) {
				_instance = new ContextBase(new ContextBaseEnforcer());
			}
			
			return _instance;
		}
		
		/** 
		 * Gets the unqualified class name from the fully qualified class name 
		 * contained in a ContextBase object's _stateEnumeration property.
		 * 
		 * @param enumeration The enumeration to iterate over to locate the fully qualified class. 
		 * Corresponds to the value returned by the <code>getStateEnumeration()</code> method 
		 * in ContextBase.
		 * 
		 * @param classDefinition The fully qualified class definition to search for in the 
		 * enumeration passed in the enumeration parameter.
		 * 
		 * @return A string representation of the unqualified class name from the
		 * fully qualified class name in the enumeration. 
		 */
		public static function getUnqualifiedStateName(enumeration:Array, classDefinition:String) : String {
			var targetStateName:String = "";
			var className:String = "";
			
			for ( var i:Number = 0; i < enumeration.length; i++ ) {
				className = enumeration[i].substring(0, 1).toUpperCase() + enumeration[i].substring(1);
				
				if ( classDefinition.indexOf(className) != -1 ) {
					targetStateName = className.substring(0, 1).toLowerCase() + className.substring(1);
					
					break;
				}
			}
			
			return targetStateName;
		}
		
		/** 
		 * A default implementation of IViewState: if the state being passed
		 * is different than the current state, hide the current state and show
		 * the new state. The targetState param is optional; it is up to subclasses
		 * to decide whether or not to employ it. You would use targetState in the subclass
		 * if there were additional tasks you wanted to perform before showing the new state,
		 * in the form of methods being called on the new state by the old state.
		 * 
		 * @param targetView Specifies which view to affect.
		 * 
		 * @param targetState (OPTIONAL) The state you are changing too.
		 */
		public function hide(targetView:ViewBase, targetState:String=null) : void {
			if ( _state != this[targetState] ) {
				_state.hide(targetView, targetState);
				
				if ( targetState != null ) {
					_state = this[targetState];
					show(targetView);
				}
			}
		}
		
		/** 
		 * Shows the state. The specific implementation is up to the concrete implementing class.
		 * 
		 * @param targetView Specifies which ViewBase to show.
		 */
		public function show(targetView:ViewBase) : void {
			_state.show(targetView);
		}
		
		/** 
		 * Retrieve a state using its name as a key.
		 * 
		 * @param stateName The name of the state you want returned.
		 * 
		 * @return Returns the state via the name specified in <code>stateName</code>.
		 */
		public function getStateByName(stateName:String) : IViewState {
			if( this[stateName] !== undefined ) {
				return this[stateName];
			}
			
			return _state;
		}
		
		/** 
		 * Get the entire list of state instance names in order to iterate 
		 * over it and perform some operation via <code>getStateByName</code>.
		 * 
		 * @return An array containing the state names of all states represented
		 * in the context object.
		 */
		public function getStateEnumeration() : Array {
			return this._stateEnumeration;
		}
		
		/** 
		 * Initialize the current context class with state instances. 
		 * Called only once from the constructor.
		 *
		 * @param currentObject The concrete subclass instance being initialized with a
		 * state enumeration.
		 */
		protected static function setStateEnumeration(currentObject:ContextBase) : void {
			//Populate the enumeration set from the enumeration object; only populate undefined properties
			if ( currentObject._stateEnumeration != null && currentObject._stateEnumerationClasses != null ) {
				for ( var i:Number = 0; i < currentObject._stateEnumerationClasses.length; i++ ) {
					//Get the class name and lower case the first character to use as a property name
					var localPropertyString:String = getQualifiedClassName(currentObject._stateEnumerationClasses[i]);
						localPropertyString = localPropertyString.split("::")[localPropertyString.split("::").length - 1];
						localPropertyString = localPropertyString.substring(0, 1).toLowerCase() + localPropertyString.substring(1);
					var staticPropertyString:String;
					var upperCaseIndices:Array = new Array();
						upperCaseIndices.push(0);
					
					//Get an index of all the upper case and numeric characters in the class name
					for ( var j:Number = 0; j < localPropertyString.length; j++ ) {
						staticPropertyString = "";
						
						var pattern:RegExp = /[A-Z]|[0-9]/;
						
						var substring:String = localPropertyString.substring(j, j + 1);
						var substringMatch:Array = substring.match(pattern);
						
						if ( substringMatch != null ) {
							if ( substringMatch[0] == substring ) {
								upperCaseIndices.push(j);
							}
						}
					}
					
					//Set the property name to upper case
					var upperCasePropertyString:String = localPropertyString.toUpperCase();
					
					/* Use the indices derived above to add in underscores;
					   we now have a String formatted to be the property name
					   of a static property in the form of MY_STATIC_PROPERTY */
					for ( var k:Number = 0; k < upperCaseIndices.length; k++ ) {
						if ( upperCaseIndices[k + 1] != null ) {
							staticPropertyString += upperCasePropertyString.substring(upperCaseIndices[k], upperCaseIndices[k + 1]);
						} else {
							staticPropertyString += upperCasePropertyString.substring(upperCaseIndices[k]);
						}
						
						if ( k < upperCaseIndices.length - 1 ) {
							staticPropertyString += "_";
						}
					}
					
					/* Create a local property with lower initial case of the classname
					   to store the state and call its constructor */
					currentObject[localPropertyString] = new currentObject._stateEnumerationClasses[i]();
					/* Create a static storing the value of the property's name to
				       expose to external clients of this class */
					getDefinitionByName(getQualifiedClassName(currentObject))[staticPropertyString] = localPropertyString;
					//Push the static key into _stateEnumeration for exposure to external classes
					currentObject._stateEnumeration.push(getDefinitionByName(getQualifiedClassName(currentObject))[staticPropertyString]);
				}
			}
		}
	}
}